/*
* Copyright (C) 2014 James Lawrence.
*
* This file is part of GrimEdi.
*
* GrimEdi is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Created by JFormDesigner on Fri Mar 29 11:34:15 GMT 2013
*/
package com.sqrt4.grimedi.ui.editor;
import com.sqrt.liblab.entry.video.AudioTrack;
import com.sqrt.liblab.entry.video.Video;
import com.sqrt4.grimedi.ui.MainWindow;
import com.sqrt4.grimedi.util.AnimatedGifCreator;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageOutputStream;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author James Lawrence
*/
public class VideoViewer extends EditorPanel<Video> {
private BufferedImage surface;
private boolean playing;
private Runnable play = new Runnable() {
public void run() {
final int framePeriod = (int) (1000f / data.fps);
int last_frame = -1;
boolean valid = true;
final WritableRaster raster = surface.getRaster();
try {
// Attempt to sync to the audio stream...
// Todo: sync is WAY off
if (data.audio.stream != null) {
AudioTrack audio = data.audio;
AudioFormat af = new AudioFormat(audio.sampleRate, audio.bits, audio.channels, true, true);
SourceDataLine sdl = (SourceDataLine) AudioSystem.getLine(new DataLine.Info(SourceDataLine.class, af));
sdl.open();
sdl.start();
byte[] buf = new byte[sdl.getBufferSize()];
audio.stream.seek(0);
int pos = 0;
final int bytesPerSample = (audio.bits / 8) * audio.channels;
final int bytesPerSec = bytesPerSample * audio.sampleRate;
final int bytesPerMs = bytesPerSec / 1000;
// VIMA is 50 frames ahead according to residual...
final int frameOff = 500;
while (valid) {
int ms = pos / bytesPerMs;
ms -= frameOff;
if (ms < 0)
ms = 0;
int frame = ms / framePeriod;
if (frame != last_frame && frame != last_frame + 1)
frame = last_frame + 1; // Interpolate, we can't drop frames!
last_frame = frame;
data.stream.setFrame(frame);
valid = playing && data.stream.readFrame(raster, data.width, data.height);
if (!valid)
break;
viewer.repaint();
int read = audio.stream.read(buf, 0, Math.min(buf.length, sdl.available()));
pos += read;
if (read == -1)
break;
sdl.write(buf, 0, read);
}
if (playing)
sdl.drain();
else
sdl.flush();
sdl.stop();
sdl.close();
} else {
// Otherwise just try and keep the FPS
long start = System.currentTimeMillis();
while (valid) {
long elapsed = System.currentTimeMillis() - start;
int frame = (int) (elapsed / framePeriod);
if (frame != last_frame && frame != last_frame + 1)
frame = last_frame + 1; // Interpolate, can't drop...
last_frame = frame;
data.stream.setFrame(frame);
valid = playing && data.stream.readFrame(raster, data.width, data.height);
viewer.repaint();
}
}
} catch (Exception e) {
MainWindow.getInstance().handleException(e);
}
stopAction.setEnabled(false);
playAction.setEnabled(true);
}
};
public VideoViewer() {
initComponents();
}
ImageIcon icon = new ImageIcon(getClass().getResource("/movie.png"));
public ImageIcon getIcon() {
return icon;
}
public void onNewData() {
surface = new BufferedImage(data.width, data.height, data.format);
viewer.setIcon(new ImageIcon(surface));
playing = false;
try {
data.stream.setFrame(0);
data.stream.readFrame(surface.getRaster(), data.width, data.height);
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
public void onHide() {
playing = false;
}
private void initComponents() {
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
viewer = new JLabel();
panel1 = new JPanel();
button1 = new JButton();
button2 = new JButton();
button3 = new JButton();
playAction = new PlayAction();
stopAction = new StopAction();
exportGifAction = new ExportGifAction();
//======== this ========
setLayout(new BorderLayout());
//---- viewer ----
viewer.setBackground(Color.black);
viewer.setOpaque(true);
viewer.setHorizontalAlignment(SwingConstants.CENTER);
add(viewer, BorderLayout.CENTER);
//======== panel1 ========
{
panel1.setLayout(new FlowLayout());
//---- button1 ----
button1.setAction(playAction);
panel1.add(button1);
//---- button2 ----
button2.setAction(stopAction);
panel1.add(button2);
//---- button3 ----
button3.setAction(exportGifAction);
panel1.add(button3);
}
add(panel1, BorderLayout.SOUTH);
// JFormDesigner - End of component initialization //GEN-END:initComponents
}
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
private JLabel viewer;
private JPanel panel1;
private JButton button1;
private JButton button2;
private JButton button3;
private PlayAction playAction;
private StopAction stopAction;
private ExportGifAction exportGifAction;
// JFormDesigner - End of variables declaration //GEN-END:variables
private class PlayAction extends AbstractAction {
private PlayAction() {
// JFormDesigner - Action initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
putValue(NAME, "Play");
putValue(SHORT_DESCRIPTION, "Play the video");
// JFormDesigner - End of action initialization //GEN-END:initComponents
}
public void actionPerformed(ActionEvent e) {
playAction.setEnabled(false);
stopAction.setEnabled(true);
playing = true;
new Thread(play).start();
}
}
private class StopAction extends AbstractAction {
private StopAction() {
// JFormDesigner - Action initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
putValue(NAME, "Stop");
putValue(SHORT_DESCRIPTION, "hammer time");
// JFormDesigner - End of action initialization //GEN-END:initComponents
}
public void actionPerformed(ActionEvent e) {
playing = false;
}
}
private class ExportGifAction extends AbstractAction {
private ExportGifAction() {
// JFormDesigner - Action initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
putValue(NAME, "Export GIF");
putValue(SHORT_DESCRIPTION, "Export the frames of this video as a GIF animation");
// JFormDesigner - End of action initialization //GEN-END:initComponents
}
public void actionPerformed(ActionEvent e) {
final AtomicBoolean cancel = new AtomicBoolean(false);
window.runAsyncWithPopup("Decoding video...", new Runnable() {
public void run() {
window.setBusyMessage("Generating GIF (frame 1/" + data.numFrames + ")");
try {
File temp = File.createTempFile("anim", ".gif");
ImageOutputStream ios = ImageIO.createImageOutputStream(temp);
AnimatedGifCreator agc = new AnimatedGifCreator(ios, BufferedImage.TYPE_INT_RGB, 14.99992f, true, 0);
WritableRaster raster = surface.getRaster();
for (int i1 = 0; i1 < data.numFrames; i1++) {
if(cancel.get())
break;
data.stream.setFrame(i1);
boolean cont = data.stream.readFrame(raster, data.width, data.height);
viewer.repaint();
agc.addFrame(surface);
window.setBusyMessage("Generating GIF (frame " + (i1 + 1) + "/" + data.numFrames + ")");
if(!cont)
break;
}
agc.finish();
ios.close();
if(!cancel.get())
if (Desktop.isDesktopSupported())
Desktop.getDesktop().open(temp);
} catch (Exception e) {
MainWindow.getInstance().handleException(e);
}
}
}, true, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
cancel.set(true);
}
});
}
}
}